package nayasis.common.model;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import nayasis.common.util.StringUtil;

/**
 * Multiple Data
 *
 * @author nayasis
 */
public class NMultiData implements Serializable, Cloneable, Iterable<NData> {

    private static final long serialVersionUID = -3169472792493027837L;
    
    private Map<Object, Integer> dataHeader = new LinkedHashMap<>();
    private List<NData>          dataBody   = new ArrayList<>();
    private static final String  NEW_LINE   = "-------------------------------------------------------------";

    public NMultiData() {}

    public void addHeader( Object columnName ) {
    	
    	if( dataHeader.containsKey( columnName ) ) return;
    	
        dataHeader.put( columnName, 0 );
    
    }

    public void add( Object columnName, Object value ) {

    	int columnSize = size( columnName );
    	int totalSize  = size();
    	
    	if( totalSize > columnSize ) {
    		
    		dataBody.get( columnSize ).put( columnName, value );
    		dataHeader.put( columnName, ++columnSize );
    		
    	} else if( totalSize == columnSize ) {
    		
    		NData newRow = new NData();
    		
    		newRow.put( columnName, value );
    		
    		dataBody.add( newRow );
    		dataHeader.put( columnName, ++columnSize );
    		
    	}

    }

    public void add( Map<Object, Object> data ) {
    	
    	for( Object key : data.keySet() ) {
    		add( key, data.get(key) );
    	}
    	
    }
    
    public void add( NData data ) {

    	for( Object key : data.keySet() ) {
    		add( key, data.get(key) );
    	}
    	
    }
    
    public int size( Object columnName ) {

    	Integer columnSize = dataHeader.get( columnName );

        if( columnSize == null ) return 0;

        return columnSize;

    }

    public int size() {

    	return dataBody.size();
    	
    }

    public Object get( Object columnName, int rowIndex ) {

        NData data = dataBody.get( rowIndex );

        if( data == null ) return null;

        return data.get( columnName );

    }

    public Object get( int columnIndex, int rowIndex ) {

    	Object key = getColumnName( columnIndex );

        return get( key, rowIndex );

    }

    public NData get( int rowIndex ) {
    	
        return dataBody.get( rowIndex );

    }
    
    public void set( Object columnName, int rowIndex, Object value ) {

        NData data = get( rowIndex );

        if( data == null ) return;

        data.put( columnName, value );
        
        int columnSize = size( columnName );
        
        dataHeader.put( columnName, Math.max(rowIndex + 1, columnSize) );

    }

    public void removeRow( int rowIndex ) {

        if( rowIndex < 0 ) return;
        
        dataBody.remove( rowIndex );

        for( Object key : dataHeader.keySet() ) {

        	int columnSize = size( key ) - 1;
        	
        	if( columnSize <= 0 ) {
        		dataHeader.remove( key );

        	} else {
        		dataHeader.put( key, columnSize );
        		
        	}
        	
        }

    }

    public void removeColumn( Object columnName ) {

        dataHeader.remove( columnName );
        
        for( NData row : dataBody ) {
        	
        	row.remove( columnName );
        	
        }

    }

    public void removeColumn( int columnIndex ) {

    	Object key = getColumnName( columnIndex );
    	
    	if( key == null ) return;

        removeColumn( key );

    }

    public void set( int columnIndex, int rowIndex, Object value ) {

    	Object key = getColumnName( columnIndex );
    	
    	if( key == null ) return;

        set( key, rowIndex, value );

    }

    public void set( int rowIndex, NData data ) {
    	
    	if( data == null ) {
    		dataBody.set( rowIndex, null );
    	}
    	
    	for( Object key : data.keySet() ) {
    		set( key, rowIndex, data.get(key) );
    	}
    	
    }
    
    public String getString( Object columnName, int rowIndex ) {

        Object returnVal = get( columnName, rowIndex );

        return ( returnVal == null ) ? null : returnVal.toString();

    }

    public String getString( int columnIndex, int rowIndex ) {

    	Object key = getColumnName( columnIndex );

        return getString( key, rowIndex );

    }

    public int getInteger( Object columnName, int rowIndex ) {
    	return Integer.parseInt( StringUtil.nvl(getString(columnName, rowIndex)) );
    }
    
    public int getInteger( int columnIndex, int rowIndex ) {
    	return Integer.parseInt( StringUtil.nvl(getString(columnIndex, rowIndex)) );
    }

    public long getLong( Object columnName, int rowIndex ) {
    	return Long.parseLong( StringUtil.nvl(getString(columnName, rowIndex)) );
    }
    
    public long getLong( int columnIndex, int rowIndex ) {
    	return Long.parseLong( StringUtil.nvl(getString(columnIndex, rowIndex)) );
    }

    public float getFloat( Object columnName, int rowIndex ) {
    	return Float.parseFloat( StringUtil.nvl(getString(columnName, rowIndex)) );
    }
    
    public float getFloat( int columnIndex, int rowIndex ) {
    	return Float.parseFloat( StringUtil.nvl(getString(columnIndex, rowIndex)) );
    }
    
    public double getDouble( Object columnName, int rowIndex ) {
    	return Double.parseDouble( StringUtil.nvl(getString(columnName, rowIndex)) );
    }

    public double getDouble( int columnIndex, int rowIndex ) {
    	return Double.parseDouble( StringUtil.nvl(getString(columnIndex, rowIndex)) );
    }
    
    public boolean containsColumn( String columnName ) {
        return dataHeader.containsKey( columnName );
    }

    public int columnSize() {
        return dataHeader.size();
    }

    public List<String> getColumnNames() {

        List<String> result = new ArrayList<>();

        for( Object key : dataHeader.keySet() ) {
            result.add( key.toString() );
        }

        return result;

    }

    public Object getColumnName( int columnIndex ) {

        if( columnIndex < 0 || columnIndex >= columnSize() ) return null;

        Iterator<Object> iterator = dataHeader.keySet().iterator();
        
        for( int i = 0; i < columnIndex - 1; i++ ) {
            iterator.next();
        }

        return iterator.next();

    }

    /**
     * Remove all elements from this.
     */
    public void clear() {
        dataHeader.clear();
        dataBody.clear();
    }
    
    /**
     * Remove only all data from this.
     */
    public void clearData() {
        dataBody.clear();
    }
    
    /* (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    public String toString() {

        StringBuffer sb = new StringBuffer();

        if( dataHeader.size() == 0 ) {

            sb.append( NEW_LINE )
              .append( "\n" )
              .append( "    NO DATA\n" )
              .append( NEW_LINE )
              .append( "\n" );

            return sb.toString();

        }

        Map<String, Integer> columnWidthList = getColumnWidth();

        String newLine = getNewLine( columnWidthList );

        sb.append( newLine );

        // 컬럼명 출력
        for( Object column : dataHeader.keySet() ) {
            sb.append( "| " );
            sb.append( getPaddingString(column, columnWidthList.get(column)) );
        }

        sb.append( "|\n" );
        sb.append( newLine );

        // 데이터 출력
        for( int i = 0, iCnt = size(); i < iCnt; i++ ) {

            for( Object column : dataHeader.keySet() ) {

                sb.append( "| " );
                sb.append( getPaddingString(get(column, i), columnWidthList.get(column)) );

            }

            sb.append( "|\n" );

        }

        sb.append( newLine );

        return sb.toString();

    }

    private String getPaddingString( Object value, int columnWidth ) {

        StringBuffer sb = new StringBuffer();

        sb.append( value );

        for( int i = 0, iCnt = columnWidth - getStringLength(value); i < iCnt; i++ ) {
            sb.append( ' ' );
        }

        return sb.toString();

    }

    private String getNewLine( Map<String, Integer> columnWidthList ) {

        StringBuffer sb = new StringBuffer();

        for( String key : columnWidthList.keySet() ) {

            Integer columnWidth = columnWidthList.get( key );

            for( int j = 0; j < columnWidth; j++ ) {

                sb.append( '-' );

            }

            sb.append( "--" );

        }

        sb.append( "-\n" );

        return sb.toString();

    }

    private Map<String, Integer> getColumnWidth() {

        Map<String, Integer> list = new LinkedHashMap<String, Integer>();

        for( Object key : dataHeader.keySet() ) {

            // 컬럼명 길이를 우선 측정
            list.put( key.toString(), getStringLength(key) );

            // 컬럼 데이터들의 길이를 모두 측정
            for( NData data : dataBody ) {

                list.put( key.toString(), Math.max(list.get(key), getStringLength(data.get(key))) );

            }

        }

        return list;

    }

    private int getStringLength( Object value ) {

        if( value == null ) return 4;

        String chkVal = value.toString();

        int result = 0;

        for( int i = 0, iCnt = chkVal.length(); i < iCnt; i++ ) {

            char c = chkVal.charAt( i );

            // 가 ~ 힣
            if( 44032 <= c && c <= 55203 ) {
                result += 2;

            // ¼ ~ ¾
            } else if( 188 <= c && c <= 190 ) {
            	result += 2;
            	
            } else {
                result ++;
            }

        }

        return result;

    }

	@Override
	public Iterator<NData> iterator() {

		final int size = size();
		
		return new Iterator<NData>() {

			int index = 0;
			
			public boolean hasNext() {
				return index < size;
			}

			public NData next() {
				
				NData row = get( index );
				
				index++;
				
				return row;

			}

			public void remove() {
				throw new UnsupportedOperationException();
			}

		};
	
	}

}
